package br.ufes.inf.lprm.cwtools.web.controller;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.Date;
import java.util.List;

import br.com.caelum.vraptor.Delete;
import br.com.caelum.vraptor.Get;
import br.com.caelum.vraptor.Path;
import br.com.caelum.vraptor.Post;
import br.com.caelum.vraptor.Resource;
import br.com.caelum.vraptor.Result;
import br.com.caelum.vraptor.Validator;
import br.com.caelum.vraptor.core.RequestInfo;
import br.com.caelum.vraptor.interceptor.download.Download;
import br.com.caelum.vraptor.interceptor.download.FileDownload;
import br.com.caelum.vraptor.interceptor.multipart.UploadedFile;
import br.com.caelum.vraptor.validator.ValidationMessage;
import br.com.caelum.vraptor.view.Results;
import br.ufes.inf.lprm.cwtools.entities.Component;
import br.ufes.inf.lprm.cwtools.entities.dao.ComponentDAO;

@Resource
@Path("/repository")
public class ComponentController {
	private final ComponentDAO dao;
	private final Result result;
	private final Validator validator;
	private RequestInfo info;

	public ComponentController(final ComponentDAO dao, final Validator validator, final Result result, final RequestInfo info){
		this.dao = dao;
		this.result = result;
		this.validator = validator;
		this.info = info;
	}
	
	private void validateComponent(final Component component) {
		final String componentName = component.getName();
		final String componentVersion = component.getVersion();
		String urlEncodedName = null;
		String urlEncodedVersion = null;
		
		try{
			urlEncodedName = URLEncoder.encode(componentName, "UTF-8");
			urlEncodedVersion = URLEncoder.encode(componentVersion, "UTF-8");
		}catch (UnsupportedEncodingException e) {
			//Not possible
		}
		
		//Validação do nome
		if(componentName.isEmpty()){
			validator.add(new ValidationMessage("Nome do componente não informado.", "Dados incompletos"));
		}else if(componentName.equals("register")){
			validator.add(new ValidationMessage("Nome do componente inválido.", "Dados inválidos"));
		}else if(componentName.length() > 50){
			validator.add(new ValidationMessage("Nome do componente deve ter no máximo 50 caracteres.", "Dados inválidos"));
		}else if(!componentName.equals(urlEncodedName)){
			validator.add(new ValidationMessage("Nome do componente deve conter apenas os caracteres a-zA-Z0-9.-_*.", "Dados inválidos"));
		}
		//Validação da descrição
		if(component.getDescription().length() > 255){
			validator.add(new ValidationMessage("Descrição do componente deve ter no máximo 255 caracteres.", "Dados inválidos"));
		}
		//Validação da versão
		if(componentVersion.length() > 10){
			validator.add(new ValidationMessage("Versão do componente deve ter no máximo 10 caracteres.", "Dados inválidos"));
		}else if(!componentVersion.equals(urlEncodedVersion)){
			validator.add(new ValidationMessage("Versão do componente deve conter apenas os caracteres a-zA-Z0-9.-_*.", "Dados inválidos"));
		}
	}
	
	private void validateComponentFile(final UploadedFile componentFile) {
		if(componentFile == null){
			validator.add(new ValidationMessage("Nenhum arquivo enviado.", "Dados incompletos"));
		}else{
			//Validação do nome
			final String componentFileName = componentFile.getFileName();
			final String componentFileType = componentFile.getContentType();
		
			if(! "application/vnd.android.package-archive".equalsIgnoreCase(componentFileType)){
				validator.add(new ValidationMessage("Tipo de arquivo enviado inválido. Envie um arquivo APK.", "Dados inválidos"));
			}else if(!componentFileName.toLowerCase().endsWith("apk")){
				validator.add(new ValidationMessage("Nome do arquivo enviado inválido. Envie um arquivo com extensão APK.", "Dados inválidos"));
			}
		}
	}
	
	@Get
	@Path({"", "/", "/component", "/component/"})
	public List<Component> list(){
		return dao.listAll();
	}
	
	@Get
	@Path("/component/new")
	public void form(){
		/*Go to new.jsp */
	}
	
	@Post
	@Path("/component/new")
	//If you wan't to insert a new file programatically, use componentController.insert(repository, component, new ComponentFile(File)); 
	public void insert(final Component component, final UploadedFile componentUploadedFile){
		long size = 0;
		
		validateComponent(component);
		validateComponentFile(componentUploadedFile);
		validator.onErrorUse(Results.logic()).forwardTo(ComponentController.class).form();
		
		File tmpComponentFile = null;
		File componentFile = null;
		try {
			tmpComponentFile = File.createTempFile("tempComponent", ".apk.tmp");
			//Read uploaded file to file system
			FileOutputStream componentFileOutputStream = new FileOutputStream(tmpComponentFile);
			InputStream uploadedFileInputStream = componentUploadedFile.getFile();
			byte[] tmpBytes = new byte[1024]; //buffer size. 1k is ok?
			int sizeRead;
			while((sizeRead = uploadedFileInputStream.read(tmpBytes)) > 0){
				size += sizeRead;
				componentFileOutputStream.write(tmpBytes, 0, sizeRead);
			}
			componentFileOutputStream.close();
			//Generating the md5 checksum from the file
			uploadedFileInputStream.reset();
			MessageDigest md = MessageDigest.getInstance("MD5");
			md.reset();
			while((sizeRead = uploadedFileInputStream.read(tmpBytes)) > 0){
				md.update(tmpBytes, 0, sizeRead);
			}
			byte[] componentMD5 = md.digest();
			BigInteger md5BigInteger = new BigInteger(componentMD5);
			String md5String = md5BigInteger.toString(16);
			while(md5String.length() < 32){
				md5String = "0" + md5String;
			}
			component.setMd5hash(md5String.substring(md5String.length()-32));
			//Setting the component date
			component.setInsertionDate(new Date());
			//Renaming the temporary component to the final name
			componentFile = new File(info.getServletContext().getRealPath("/WEB-INF/repository/" + component.getMd5hash() + ".apk"));
			if(!tmpComponentFile.renameTo(componentFile)){
				throw new Exception();
			}
			//Setting the file size
			component.setSize(size);
			//Inserting
			dao.insert(component);
			
		}catch(Exception e) {
			if(tmpComponentFile != null){
				tmpComponentFile.delete();
			}
			if(componentFile != null){
				componentFile.delete();
			}
			result.use(Results.status()).badRequest((String) null);
		}
		
		result.redirectTo(this).list();
	}

	@Delete
	@Path("/component/{component.id}")
	public void delete(final Component component){
		File componentFile = new File(info.getServletContext().getRealPath("/WEB-INF/repository/" + dao.find(component).getMd5hash() + ".apk"));
		componentFile.delete();
		dao.delete(component);
		
		result.redirectTo(this).list();
	}
	
	@Get
	@Path("/component/{component.id}/download")
	public Download download(final Component component){
		Component componentComplete = dao.find(component);
		File componentFile = new File(info.getServletContext().getRealPath("/WEB-INF/repository/" + componentComplete.getMd5hash() + ".apk"));
		String componentFileName = componentComplete.getName() + "-" + componentComplete.getVersion() + ".apk";
        return new FileDownload(componentFile, "application/vnd.android.package-archive", componentFileName); 
	}
	
	@Get
	@Path("/component/{component.md5hash}/downloadByHash")
	public Download downloadByHash(final Component component){
		return download(component); 
	}
}
